package core

import (
	"yuanchang/spider/http"
	"yuanchang/spider/schedule"
	"yuanchang/spider/spider"
)

type Engine struct {
	Spiders  map[string]*spider.Spider
	Schedule schedule.ScheduleInterface
	filter   *Filter
	download *Downloader
	exit     chan bool
}

func (e *Engine) Run() {
	e.startSpiders()

	e.download.Start()

	e.callDownloader()

	e.callSpider()
}

func (e *Engine) LoadSpiders(spiders []*spider.Spider) {
	for _, item := range spiders {
		e.Spiders[item.Name] = item
	}
}

// from all spiders get requests
// the requests send to schedule
func (e *Engine) startSpiders() {
	for _, s := range e.Spiders {
		for _, url := range s.StartUrls {
			if req, err := http.NewRequest("GET", url); err == nil {
				req.SpiderName = s.Name
				req.CallBackName = "Response"
				e.Schedule.PushRequest(req)
			}
		}
	}
}

// from schedule get requests
// the requests send to downloader
func (e *Engine) callDownloader() {
	go func() {
		for {
			if req := e.Schedule.PopRequest(); req != nil {
				e.download.Commit(req)
			}
		}
	}()
}

// receive  response to call spider
func (e *Engine) callSpider() {
	var (
		exit bool
	)
	for {
		select {
		case res := <-e.download.GetResponse():
			if s, ok := e.Spiders[res.SpiderName]; ok {
				if callback, ok := s.Responses[res.CallBackName]; ok {
					if req := <-callback(s, res); req != nil {
						e.Schedule.PushRequest(req)
					} else {
						go func() {
							e.exit <- true
						}()
					}
				}
			}
		case exit = <-e.exit:
		}
		if exit {
			break
		}
	}
}

// new engine entity
func NewEngine(schedule schedule.ScheduleInterface) *Engine {
	engine := new(Engine)
	engine.Schedule = schedule
	engine.Spiders = make(map[string]*spider.Spider)
	engine.filter = NewFilter(defaultFilterName)
	engine.download = NewDownloader()
	engine.exit = make(chan bool)
	return engine
}
